#ifndef ALFS_POINTERS_NOTIFYINGCHECKINGPTR_HPP
#define ALFS_POINTERS_NOTIFYINGCHECKINGPTR_HPP
#include    <alfs/config.hpp>

// File [alfs/pointers/NotifyingPtr.hpp].
//
// alfs::NotifyingPtr<>.
// A shared pointer where operator-> throws if the referred object has been destroyed,
// and which in turns notifies an "owner" object of any premature referred object
// destruction.  The "owner" is not notified of destruction due to refcount -> 0.
//
// (c) copyright Alf P. Steinbach 2008.
// Use, modification and distribution are subject to the "New BSD License"
// as listed at <url: http://www.opensource.org/licenses/bsd-license.php>.

#include    "CheckingPtr.hpp"                               // alfs::CheckingPtr etc.

namespace alfs {

    namespace detail {

#       define  ThisClass       NotifyingRefCountedDeleter

        template< typename T, typename NotifierFunc >
        class ThisClass
            : public AutoClearingRefCountedDeleter<T>
        {
        static_assert( (alfs::IsConvertibleFromTo< T*, alfs::destructionEvent::Source* >::yes), "" );
        // T must be directly or indirectly publicly derived from alfs::destructionEvent::Source.

        private:
            typedef AutoClearingRefCountedDeleter<T>        BaseClass;

            ThisClass( ThisClass const& );              // No such.
            ThisClass& operator=( ThisClass const& );   // No such.

            NotifierFunc    myNotifyClearedFunc;

            virtual void onCleared()                    // ovrd
                throw()
            {
                try
                {
                    myNotifyClearedFunc();
                }
                catch( ... )
                {
                    assert(
                        "alfs::detail::NotifyingRefCountedDeleter::onCleared: "
                        "client's notifier function threw an exception"
                        && false
                        );
                    std::terminate();
                }
            }

        protected:
            using BaseClass::referent;

            ThisClass( NotifierFunc notifier, T* p )
                throw()
                : BaseClass( p )
                , myNotifyClearedFunc( notifier )
            {}

        public:
            static ThisClass* createWith( NotifierFunc notifier, T* p )
                ALFS_MAY_THROW
            {
                // TODO: Replace this with ownership-transfer smart pointer using alfs::destroyPointee.
                try
                {
                    return new ThisClass( notifier, p );
                }
                catch( ... )
                {
                    alfs::destroyPointee( p );
                    throw;
                }
            }
        };
#       undef   ThisClass

    }    // detail


#   define  ThisClass       NotifyingPtr

    template< typename T > class AutoNotifyingPtr;

    template< typename T >
    class ThisClass
        : protected CheckingPtr<T>
    {
    template< typename > friend class ThisClass;

    private:
        typedef CheckingPtr<T>  BaseClass;

    protected:
        template< typename NotifierFunc >
        ThisClass(
            T*                                                      p,
            detail::NotifyingRefCountedDeleter<T, NotifierFunc>&    deleter
            )
            throw()
            : BaseClass( p, deleter )
        {}

        template< typename U >
        ThisClass( T* p, ThisClass<U> const& managingPtr )
            throw()
            : BaseClass( p, managingPtr )
        {}

    public:
        typedef AutoNotifyingPtr<T>     Auto;

        // Default constructor makes void.
        ThisClass() throw() {}

        // Original ownership transfer to ThisClass.
        template< typename U, typename NotifierFunc >
        ThisClass(
            NotifierFunc    notifier,
            U*              p,
            typename boost::enable_if_c<
                    alfs::IsConvertibleFromTo< U*, alfs::destructionEvent::Source* >::yes &&
                    alfs::IsConvertibleFromTo< U*, T* >::yes
                >::type* = 0
            )
            ALFS_MAY_THROW
            : BaseClass(
                p,
                *detail::NotifyingRefCountedDeleter<U, NotifierFunc>::createWith( notifier, p )
                )
        {}

        // Logical "implicit upcast".
        template< typename U >
        ThisClass( ThisClass<U> const& other )
            throw()
            : BaseClass( other )
        {}

        void swap( ThisClass& other )
            throw()
        {
            BaseClass::swap( other );
        }

        using BaseClass::makeVoid;
        using BaseClass::isVoid;
        using BaseClass::hasValidReferent;
        using BaseClass::releaseReferent;
        using BaseClass::releasedReferent;
        using BaseClass::operator->;
        using BaseClass::unchecked;
        using BaseClass::validRawPointerOrZero;
        using BaseClass::validRawPointerOrX;
        using BaseClass::asCheckingPtr;

        NotifyingPtr<T> asNotifyingPtr() const
        {
            return *this;
        }
    };
#       undef   ThisClass


#   define  ThisClass       AutoNotifyingPtr

    template< typename T >
    class ThisClass
        : public NotifyingPtr< T >
    {
    template< typename > friend class ThisClass;

    protected:
        typedef typename WithDestructionEventWrapper<T>::Type   WrappedT;
        typedef NotifyingPtr<T>                         BaseClass;

    public:
        // Logical "implicit upcast".
        template< typename U >
        ThisClass( ThisClass<U> const& other )
            throw()
            : BaseClass( other )
        {}

        // Logical "implicit downcast", special case.
        template< typename U >
        ThisClass( NotifyingPtr<U> const& other )
            throw()
            : BaseClass( other )
        {}

        // Void pointer as default is in support of using ThisClass's in e.g. arrays.
        ThisClass()
            throw()
            : BaseClass( 0 )
        {}

        // This constructor is not instantiated if it's not used (we're in magic template
        // world).  I.e. it does not impose a requirement of T being default-constructible.
        template< typename NotifierFunc >
        ThisClass( NotifierFunc notifier, detail::NewNoArgs )
            ALFS_MAY_THROW
            : BaseClass( notifier, new WrappedT )
        {}

        template< typename NotifierFunc, typename U >
        ThisClass( NotifierFunc notifier, U const& u )
            ALFS_MAY_THROW
            : BaseClass( notifier, new WrappedT( u ) )
        {}

        template< typename NotifierFunc, typename U, typename V >
        ThisClass( NotifierFunc notifier, U const& u, V const& v )
            ALFS_MAY_THROW
            : BaseClass( notifier, new WrappedT( u, v ) )
        {}

        template< typename NotifierFunc, typename U, typename V, typename W >
        ThisClass( NotifierFunc notifier, U const& u, V const& v, W const& w )
            ALFS_MAY_THROW
            : BaseClass( notifier, new WrappedT( u, v, w ) )
        {}

        template< typename NotifierFunc, typename U, typename V, typename W, typename X >
        ThisClass( NotifierFunc notifier, U const& u, V const& v, W const& w, X const& x )
            ALFS_MAY_THROW
            : BaseClass( notifier, new WrappedT( u, v, w, x ) )
        {}

        template< typename NotifierFunc, typename U, typename V, typename W, typename X, typename Y >
        ThisClass( NotifierFunc notifier, U const& u, V const& v, W const& w, X const& x, Y const& y )
            ALFS_MAY_THROW
            : BaseClass( notifier, new WrappedT( u, v, w, x, y ) )
        {}
    };
#   undef   ThisClass

}  // alfs

#endif
